#pragma once;

#include <vector>
#include <string>
#include "TMatrix.h"
#include <io.h>
#include <algorithm>
#include <fstream>
#include "common_utils.h"

namespace PackIt
{
	class CPackBuffer;
}

bool LoadFile(const char* szFileName, PackIt::CPackBuffer& packBuffer);

bool IsValidDirectory(const char* szDir);

std::string GetFileExtension(const char* szFile);

std::string GetFileNameOnly(const char* szPathFileName);

class ValidFileExtPred
{
public:
	ValidFileExtPred(const char* szExts[], int size): m_szExts(szExts), m_iSize(size) {}

	bool operator() (const char* szFile) const
	{
		for(int i = 0; i<m_iSize; i++)
		{
			if(_stricmp(GetFileExtension(szFile).c_str(), m_szExts[i]) == 0)
				return true;
		}
		return false;

	}

private:
	const char**	m_szExts;
	int				m_iSize;
};

//////////////////////////////////////////////////////////////////////////
// class CFwdMatFileListLoader
//

template<typename T, int RECORD_DIM>
class CFwdMatFileListLoader
{
public:
	CFwdMatFileListLoader()
	{
		Clear();
	}
	CFwdMatFileListLoader(const char* szFileDataList)
	{
		SetFileDataList(szFileDataList);
	}

	bool IsValid() const
	{
		if(m_bIsEmpty)
			return false;

		if(m_szFiles.empty())
			return false;

		if(m_nCurrentFileIndex >= (int)m_szFiles.size())
			return false;

		if(m_nCurrentFileIndex == m_szFiles.size() - 1 && m_nCurrentRecordIndex >= m_nCurrentFileRecordCount)
			return false;

		return true;
	}

	bool SetFileDataList(const std::vector<std::string>& vszFileList)
	{
		Clear();
		m_szFiles.assign(vszFileList.begin(),vszFileList.end());

		if(!IsValid())
		{
			if(LoadNewFile(true))
				m_bIsEmpty = false;	
		}

		return !m_bIsEmpty;
	}

	bool SetFileDataList(const char* szFileDataList)
	{
		Clear();

		std::ifstream in(szFileDataList);
		if(!in.is_open())
		{
			std::cout<<"Cannot load file list: "<<szFileDataList<<std::endl;
			return false;
		}

		std::vector<std::string> vszFileList;

		std::string szFile;
		while(in>>szFile)
		{
			vszFileList.push_back(szFile);
		}
		in.close();

		return SetFileDataList(vszFileList);
	}

	const T* GetRecord()
	{
		if(!IsValid())
			return NULL;

		if(!CheckFileLoaded())
			return NULL;

		return m_matData.GetRowPtr(m_nCurrentRecordIndex);
	}

	void NextRecord()
	{
		if(!IsValid())
			return;

		if(!CheckFileLoaded())
			return;

		if(m_nCurrentRecordIndex >= m_matData.GetRows() - 1)
		{
			if(!LoadNewFile(false))
			{
				m_bIsEmpty = true;
			}
		}
		else
		{
			++m_nCurrentRecordIndex;		
		}
		m_nTotalRecordIndex++;
	}

	bool IsEmpty() const { return m_bIsEmpty; }

	int LoadRecords(FAST::TMatrix<T>& matData, int iRecords)
	{
		int iDim = GetDataDim();
		matData.NewSize(iRecords, iDim);
		int index = 0;
		for(index = 0; index < iRecords && !IsEmpty(); index++)
		{
			TArrayCpy(matData.GetRowPtr(index), GetRecord(), iDim);
			NextRecord();
		}
		if(index != iRecords)
		{
			TMatrix<T> tempMat;
			tempMat.ImportData(matData.GetPtr(), index, iDim);
			matData.ImportData(tempMat.GetPtr(), index, iDim);
		}
		ReleaseCache();
		return index;
	}

	bool ReSet()
	{
		std::vector<std::string> vTmpFiles = m_szFiles;
		return SetFileDataList(vTmpFiles);
	}

	int GetDataDim() const { return RECORD_DIM; }

	void ReleaseCache()
	{
		m_matData.Destroy();
		m_bReleased = true;
		std::cout<<"Release data cache"<<std::endl;
	}

	std::string GetCurrentFileName() const 
	{
		if(IsValid())
			return m_szFiles[m_nCurrentFileIndex]; 
		return "";
	}

	std::vector<std::string> GetFileList() const { return m_szFiles; }

	int GetIndex() const { return m_nTotalRecordIndex; }

	int GetTotalRecords() const
	{
		int iTotalRecords = 0;
		for(size_t i = 0; i<m_szFiles.size(); i++)
		{
			int nTSize, nRows, nCols;
			if(ReadTMatrixHeader(m_szFiles[i].c_str(), nTSize, nRows, nCols) != -1 &&
				nTSize == sizeof(T) && nCols == RECORD_DIM)
			{
				iTotalRecords += nRows;
			}
		}
		return iTotalRecords;
	}
private:
	void Clear()
	{
		m_nCurrentFileIndex = -1;
		m_nCurrentRecordIndex = 0;
		m_nTotalRecordIndex = 0;
		m_nCurrentFileRecordCount = 0;
		m_bIsEmpty = true;
		m_bReleased = false;
		m_szFiles.clear();
	}

	bool LoadNewFile(bool bReleaseCache)
	{
		m_bIsEmpty = true;
		while(m_nCurrentFileIndex < (int)m_szFiles.size()-1)
		{
			m_nCurrentFileIndex++;
			std::cout<<"load new file: "<<m_szFiles[m_nCurrentFileIndex]<<std::endl;

			bool bLoadResult = false;
			int nTSize, nRows, nCols;
			if(bReleaseCache)
			{
				if(ReadTMatrixHeader(m_szFiles[m_nCurrentFileIndex].c_str(), nTSize, nRows, nCols) != -1 &&
					nTSize == sizeof(T) && nCols == RECORD_DIM)
				{
					bLoadResult = true;
				}
				else
				{
					std::cout<<"Read matrix header failed!"<<std::endl;
				}
				ReleaseCache();
			}
			else if(m_matData.Read(m_szFiles[m_nCurrentFileIndex].c_str(), true) != -1 && m_matData.GetCols() == RECORD_DIM)
			{
				bLoadResult = true;
				nRows = m_matData.GetRows();
				m_bReleased = (nRows <= 0);
			}

			if(bLoadResult)
			{
				m_bIsEmpty = (nRows <= 0);
				m_nCurrentFileRecordCount = nRows;
				m_nCurrentRecordIndex = 0;
				if(!m_bIsEmpty)
				{
					break;
				}
			}
			else
			{
				std::cout<<"Load file: "<<m_szFiles[m_nCurrentFileIndex]<<" failed! File may not exist or file type not match"<<std::endl;
			}
		}

		return !m_bIsEmpty;
	}

	bool CheckFileLoaded()
	{
		if(m_bReleased)
		{
			std::cout<<"Data file reload"<<std::endl;
			if(m_matData.Read(m_szFiles[m_nCurrentFileIndex].c_str(), true) == -1)
			{
				std::cout<<"Can not load file "<<m_szFiles[m_nCurrentFileIndex]<<std::endl;
				return false;
			}
			m_bReleased = false;
		}
		return true;
	}

	FAST::TMatrix<T>	m_matData;
	std::vector<std::string> m_szFiles;

	int	m_nCurrentFileIndex;
	int m_nCurrentRecordIndex;
	int m_nCurrentFileRecordCount;
	int m_nTotalRecordIndex;

	bool m_bIsEmpty;
	bool m_bReleased;
};

//////////////////////////////////////////////////////////////////////////
// class CRandMatFileListLoader
//

template<typename T, int RECORD_DIM>
class CRandMatFileListLoader: public CFwdMatFileListLoader<T, RECORD_DIM>
{
public:
	int LoadSortedRecords(int* pSortedRecordIndex, int iRecords, T* pDst)
	{
		if(!ReSet())
			return 0;

		std::sort(pSortedRecordIndex, pSortedRecordIndex + iRecords);

		int iCopyed = 0;
		while( iCopyed < iRecords && !IsEmpty())
		{
			if(pSortedRecordIndex[iCopyed] == GetIndex())
			{
				TArrayCpy(pDst, GetRecord(), RECORD_DIM);
				pDst += RECORD_DIM;
				iCopyed++;
			}
			NextRecord();
		}

		assert(iCopyed == iRecords);
		ReleaseCache();
		return iCopyed;
	}

};

//////////////////////////////////////////////////////////////////////////
// class CCycledMatFileListLoader
//

template<typename T, int RECORD_DIM>
class CCycledVisitMatFileListLoader
{
public:
	CCycledVisitMatFileListLoader(): m_iMarkedRecords(0), m_bWholeCycleVisited(false), m_bValid(false) {}
	bool SetFileDataList(const std::vector<std::string>& vszFileList)
	{
		m_iMarkedRecords = 0;
		m_bWholeCycleVisited = false;
		m_bValid = m_DataLoader.SetFileDataList(vszFileList);
		if(m_bValid)
		{
			m_iMarkedRecords = m_DataLoader.GetIndex();
		}
		return m_bValid;
	}

	bool SetFileDataList(const char* szFileDataList)
	{
		m_iMarkedRecords = 0;
		m_bWholeCycleVisited = false;
		m_bValid = m_DataLoader.SetFileDataList(szFileDataList);
		if(m_bValid)
		{
			m_iMarkedRecords = m_DataLoader.GetIndex();
		}
		return m_bValid;
	}
	int LoadRecords(FAST::TMatrix<T>& matData, int iRecords)
	{
		int iDim = m_DataLoader.GetDataDim();
		matData.NewSize(iRecords, iDim);
		int index = 0;
		for(index = 0; index < iRecords && !IsWholeCycleScaned(); index++)
		{
			TArrayCpy(matData.GetRowPtr(index), GetRecord(), iDim);
			NextRecord();
		}
		if(index != iRecords)
		{
			TMatrix<T> tempMat;
			tempMat.ImportData(matData.GetPtr(), index, iDim);
			matData.ImportData(tempMat.GetPtr(), index, iDim);
		}
		ReleaseCache();
		return index;
	}

	void SetMark()
	{
		if(IsValid())
			m_iMarkedRecords = m_DataLoader.GetIndex();
	}
	bool IsWholeCycleScaned() const { return m_bWholeCycleVisited; }
	bool IsValid() const { return m_bValid; }
	const T* GetRecord()
	{
		if(IsValid())
			return m_DataLoader.GetRecord();
		return NULL;
	}
	void NextRecord()
	{
		if(!IsValid())
			return;

		if(m_DataLoader.IsEmpty())
		{
			m_bValid = m_DataLoader.ReSet();
		}
		else
			m_DataLoader.NextRecord();

		if(!m_bWholeCycleVisited)
		{
			if(m_DataLoader.GetIndex() == m_iMarkedRecords)
				m_bWholeCycleVisited = true;
		}
	}
	void ReleaseCache()
	{
		m_DataLoader.ReleaseCache();
	}
	std::string GetCurrentFileName() const { return m_DataLoader.GetCurrentFileName(); }
	std::vector<std::string> GetFileList() const { return m_DataLoader.GetFileList(); }

	int GetTotalRecords() const 
	{
		if(IsValid())
			return m_DataLoader.GetTotalRecords(); 
		return 0;
	}
private:
	bool m_bValid;
	int m_iMarkedRecords;
	bool m_bWholeCycleVisited;
	CRandMatFileListLoader<T, RECORD_DIM>	m_DataLoader;
};

//////////////////////////////////////////////////////////////////////////
// class CDiskFileLoader
//

class CDiskFileLoader
{
public:
	template<typename FileNamePred>
	bool ParseFileDirectory(const char* szDirectory, const FileNamePred& pred)
	{
		Clear();
		if(!IsValidDirectory(szDirectory))
		{
			std::cout<<"Directory "<<szDirectory<<" is not a valid directory!"<<std::endl;
			return false;
		}

		CollectFile(szDirectory, pred);
		std::cout<<"image file count for bootstrap: "<<m_fileNames.size()<<std::endl;

		return true;
	}

	int GetTotalFileCount() const { return (int)m_fileNames.size(); }

	const std::vector<std::string>& GetFiles() const { return m_fileNames; }

	void RandomFiles()
	{
		//random shuffle the files
		random_shuffle(m_fileNames.begin(), m_fileNames.end(), RandRange());
	}

private:
	template<typename FileNamePred>
	int CollectFile(const char* szDir, const FileNamePred& pred)
	{
		std::string szFindFiles = std::string(szDir) + "\\*.*";
		_finddata_t disk_file;    
		intptr_t hFile;

		if ((hFile = _findfirst(szFindFiles.c_str(), &disk_file)) < 0)
			return 0;

		int count = 0;
		/* find the rest of the img files */
		while( _findnext( hFile, &disk_file ) == 0 )
		{
			if (strcmp(disk_file.name, ".") == 0)
				continue;
			if (strcmp(disk_file.name, "..") == 0)
				continue;

			if ((disk_file.attrib & _A_SUBDIR) != _A_SUBDIR)
			{
				if (!pred(disk_file.name))
					continue;
				m_fileNames.push_back(std::string(szDir) + std::string("\\") + disk_file.name);
			}
			else if (disk_file.attrib & _A_SUBDIR)
			{
				std::string szNewDir = std::string(szDir) + std::string("\\") + disk_file.name;
				CollectFile(szNewDir.c_str(), pred);
			}
		}
		_findclose( hFile );

		return count;	
	}

	void Clear()
	{
		m_fileNames.clear();

	}

	std::vector<std::string>	m_fileNames;
};
